﻿Imports Microsoft.Xna.Framework
Imports Microsoft.Xna.Framework.Graphics

Public Class GameObject
    Inherits XNAObject
    Implements IGameObjectParent

#Region " Fields "
    Private mWorld As Matrix
    Private mModels As New List(Of MyModel)
    Private mChildren As New List(Of GameObject)
    Private mIsSolid As Boolean = True
    Private mOffset As Vector3 = Vector3.Zero
    Private mBoundingSphere As BoundingSphere
    Private mModel As MyModel

    ''' <summary>
    ''' Game objects are organised in a tree structure. Parent object.
    ''' </summary>
    Private mParent As IGameObjectParent
#End Region ' Fields

#Region " Properties "
    Public ReadOnly Property Children() As List(Of GameObject) Implements IGameObjectParent.Objects
        Get
            Children = mChildren
        End Get
    End Property

    Public ReadOnly Property World() As Matrix
        Get
            If Not MatricesValid Then _
            CalculateWorld()
            World = mWorld
        End Get
    End Property

    Public ReadOnly Property Models() As List(Of MyModel)
        Get
            Models = mModels
        End Get
    End Property

    ''' <summary>
    ''' Model currently in use
    ''' </summary>
    Public ReadOnly Property Model() As MyModel
        Get
            Model = mModel
        End Get
    End Property

    Public Property Parent() As IGameObjectParent
        Get
            Parent = mParent
        End Get
        Set(ByVal value As IGameObjectParent)
            mParent = value
        End Set
    End Property

    Public ReadOnly Property BoundingSphere() As BoundingSphere
        Get
            If Not MatricesValid Then CalculateWorld()
            BoundingSphere = mBoundingSphere
        End Get
    End Property

    ''' <summary>
    ''' Should other object be able to overlap this object? If we are talking
    ''' about a cloud, then probably yes. If we are talking about a tank, then
    ''' probably not.
    ''' </summary>
    Public Property IsSolid() As Boolean
        Get
            IsSolid = mIsSolid
        End Get
        Set(ByVal value As Boolean)
            mIsSolid = value
        End Set
    End Property

    ''' <summary>
    ''' Amount the the object should the object be offset to align its
    ''' centre point correctly
    ''' </summary>
    ''' <returns>Offset vector3, pre-adjusted for rotation</returns>
    Public Property Offset() As Vector3
        Get
            Offset = Vector3.Transform(mOffset, Matrix.CreateFromQuaternion(Rotation))
        End Get
        Set(ByVal value As Vector3)
            mOffset = value
        End Set
    End Property
#End Region ' Properties

    Private Sub CalculateWorld()
        mWorld = Matrix.CreateScale(Scale) * Matrix.CreateFromQuaternion(Rotation) * Matrix.CreateTranslation(Position + Offset)
        MatricesValid = True
        CalculateBoundingSphere()
    End Sub

    Private Sub CalculateBoundingSphere()
        Dim i As Integer = 0

        ' If we have a model, then start with its bounding sphere
        If Model IsNot Nothing Then
            Dim bs As BoundingSphere = Model.BoundingSphere
            Dim centre As Vector3 = Vector3.Transform(bs.Center, World)

            mBoundingSphere = New BoundingSphere(centre, bs.Radius * Math.Max(Math.Max(Scale.X, Scale.Y), Scale.Z))
            i += 1
        End If

        ' Ensure bounding sphere is large enough to contain all children
        For Each gObj As GameObject In Children
            ' If we still don't have a bounding sphere yet (i.e. we have
            '  no model) then use the bounding shpere of the first child.
            If i = 0 Then
                mBoundingSphere = gObj.BoundingSphere
            Else
                ' Enlarge bounding sphere to contain object
                mBoundingSphere = BoundingSphere.CreateMerged(BoundingSphere, gObj.BoundingSphere)
            End If
            i += 1
        Next
    End Sub

    Protected Sub CalculateCurrentModel(ByVal camera As Camera3D)
        ' If we have no models then return
        If Models.Count = 0 Then Exit Sub

        Dim bs As BoundingSphere = mModels(0).BoundingSphere
        Dim distanceAway As Single = Vector3.Distance(camera.Position, bs.Center)
        For i As Integer = mModels.Count - 1 To 0 Step -1
            If distanceAway > mModels(i).MinimumDistance Then mModel = mModels(i)
        Next

        mModel = mModels(0)
    End Sub

#Region " Initialization "

    Public Sub New(ByVal GameManager As GameManager)
        MyBase.New(GameManager)
    End Sub

    Public Sub New(ByVal GameManager As GameManager, ByVal position As Vector3)
        MyBase.New(GameManager)
        Me.Position = position
    End Sub

    Public Sub New(ByVal GameManager As GameManager, ByVal Parent As IGameObjectParent)
        MyBase.New(GameManager)
        mParent = Parent
    End Sub

    Public Sub New(ByVal GameManager As GameManager, ByVal Parent As IGameObjectParent, ByVal position As Vector3)
        MyClass.New(GameManager, Parent)
        Me.Position = position
    End Sub

    Public Overridable Sub Initialize()
    End Sub

    Public Overridable Sub LoadContent()
    End Sub

    Public Overridable Sub UnloadContent()
    End Sub

#End Region

#Region " Update and Draw "

    ''' <summary>
    ''' Allows the game object to run logic.
    ''' </summary>
    Public Overridable Sub Update(ByVal elapsedSeconds As Single)
    End Sub

    '''<summary>
    ''' Allows the game object to handle user input.
    ''' </summary>
    Public Overridable Sub HandleInput(ByVal input As IGamePadInputManager)
    End Sub

    ''' <summary>
    ''' This is called when the object should draw itself.
    ''' </summary>
    Public Overridable Sub Draw(ByVal elapsedSeconds As Single, ByVal camera As Camera3D)
        CalculateCurrentModel(camera)

        If Model IsNot Nothing Then _
            Model.Draw(camera, World)

        For Each gObj As GameObject In Children
            'TODO - remove this line
            'If camera.BoundingFrustum.Intersects(gObj.BoundingSphere) Then _
            gObj.Draw(elapsedSeconds, camera)
        Next
    End Sub

#End Region ' Update and Draw

    Public Function GetCollisionCandidates(ByVal mObj As MovableObject) As List(Of GameObject)
        GetCollisionCandidates = New List(Of GameObject)
        If IsSolid AndAlso Model IsNot Nothing AndAlso Model.BoundingSphere.Intersects(mObj.BoundingSphere) Then
            GetCollisionCandidates.Add(Me)
        End If

        For Each candidate As GameObject In Children
            GetCollisionCandidates.AddRange(candidate.GetCollisionCandidates(mObj))
        Next
    End Function

    Protected Overrides Sub InvalidateMatrices() Implements IGameObjectParent.InvalidateMatrices
        MyBase.InvalidateMatrices()
        If Parent IsNot Nothing Then Parent.InvalidateMatrices()
    End Sub
End Class